class Compile{
    constructor(el,vm){
        // this.el=el;
        this.el=this.isElementNode(el)?el:document.querySelector(el);
        this.vm=vm;
        if(this.el){ //如果有这样一个元素 我们才开始编译
            //    1.先把这些真实的dom移入到内存中 fragment
            let fragment=this.nodeToFragment(this.el);
            //    2.编译=>提取想要的元素节点 v-model和文本节点{{}}
            this.compile(fragment);
            //    3.把编译好的fragment再塞回页面中
            this.el.appendChild(fragment);
        }
    }
    /*专门写一些辅助的方法*/
    isElementNode(node){
        return node.nodeType===1;//1是元素 2是属性 3是文本...
    }
    //是不是指令
    isDirective(name){
        return name.includes('v-');
    };
    /*核心的方法*/
    compileElement(node){
        //    带v-model v-text
        let attrs=node.attributes;//取出当前节点的属性
        // console.log('attrs---------->',attrs);
        Array.from(attrs).forEach(attr=>{
            //    判断属性名字是不是包含v-
            let attrName=attr.name;
            if(this.isDirective(attrName)){
                //    取到对应的值放到节点中
                let expr=attr.value;

                let [,type]=attrName.split('-');//type 为model text html....
                // console.log('指令v-后面的部分---------->',type);
                //    node this.vm.$data expr
                CompileUtil[type](node,this.vm,expr);
            //    事件的绑定 用下面这个v-on
            //     let [directiveName,eventName]=type.split(':');
            //     CompileUtil[directiveName](node,this.vm,expr,eventName);
            }
        })
    };
    compileText(node){
        //    形如{{a}} {{b}} {{c}}
        let expr=node.textContent;//去文本中的内容
        // console.log('text---------->',expr);
        let reg=/\{\{([^}]+)\}\}/g;
        if(reg.test(expr)){
            //    node this.vm.$data expr
            CompileUtil['text'](node,this.vm,expr)
        }
    };
    compile(fragment){

        //需要递归
        let  childNodes=fragment.childNodes;
        // console.log("childNodes----->",childNodes);
        Array.from(childNodes).forEach(node=>{
            if(this.isElementNode(node)){
                //是元素节点 还需要深入的检查
                // console.log("element----->",node);
                this.compile(node);
                // 这里需要编译元素
                this.compileElement(node);
                //
            }else{
                //    文本节点
                //     console.log("text----->",node);
                this.compileText(node);
            }
        })

    };
    nodeToFragment(el){
        //将el全部放在内存中
        //文档碎片 内存中的dom节点
        let fragment=document.createDocumentFragment();
        let firstChild;
        while(firstChild=el.firstChild){//图示意
            fragment.appendChild(firstChild)
        }
        return fragment;//内存中的节点

    }
}
CompileUtil={
    getVal(vm,expr){
        // console.log('expr------>'+expr);
        expr=expr.split('.');//[message,a]

        return expr.reduce((prev,next)=>{//vm.$data.a
            return prev[next];
        },vm.$data)
    },
    getTextVal(vm,expr){
      return expr.replace(/\{\{([^}]+)\}\}/g,(...argumments)=>{
          // console.log(argumments);
          return this.getVal(vm,argumments[1]);
        })

    },
    text(node,vm,expr){//文本处理
        // console.log('文本表达式------>'+expr);
        let updateFn=this.updater['textUpdater'];
        //{{message.a}}=>hello,xyy;
        let value=this.getTextVal(vm,expr);
        //{{a}} {{b}}
        expr.replace(/\{\{([^}]+)\}\}/g,(...argumments)=>{
            new Watcher(vm,argumments[1],(newValue)=>{
                // 如果数据变化了，文本节点需要重新获取依赖的属性更新文本中的内容
                updateFn&&updateFn(node,this.getTextVal(vm,expr));
            });
        });

        updateFn&&updateFn(node,value);
    },
    setVal(vm,expr,value){//
        expr=expr.split('.');//[message,a]
        //要收敛
        return expr.reduce((prev,next,currentIndex)=>{
            if(currentIndex===expr.length-1){
                return prev[next]=value;
            }
            return prev[next];
        },vm.$data)
    },
    model(node,vm,expr){//输入框处理
        let updateFn=this.updater['modelUpdater'];
        //这里应该加一个监控 数据变了 应该调用这个watch的callback
        new Watcher(vm,expr,(newValue)=>{
          // 当值变化后调用cb 将新的值传递过来
            updateFn&&updateFn(node,this.getVal(vm,expr));
        });
        //最后一步绑定事件
        node.addEventListener('input',(e)=>{
            let newValue=e.target.value;
            this.setVal(vm,expr,newValue);
        })
        updateFn&&updateFn(node,this.getVal(vm,expr));
    },
    on(node,vm,expr,eventName){
        node.addEventListener(eventName,(e)=>{
            vm[expr].call(vm,e);
        })
    },
    updater:{
        //文本更新
        textUpdater(node,value){
            node.textContent=value;
        },
        //输入框更新
        modelUpdater(node,value){
            node.value=value;
        }
    }
};
